Skip to content

style(runner): unify editor shell and panels on the design's type/grid scales - #248

Merged
demtario merged 6 commits into
masterfrom
feature/styles-with-ai-sidebar
Aug 21, 2026
Merged

style(runner): unify editor shell and panels on the design's type/grid scales#248
demtario merged 6 commits into
masterfrom
feature/styles-with-ai-sidebar

Conversation

@sl01k

@sl01k sl01k commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Summary

A design-alignment pass over the runner's authoring shell and both drawers, built against the Sandbox Figma (48:6560 light / 31:6438 dark) and the docs assistant on handsontable.com/docs.

Typography & spacing

  • One type scale in theme.type — base 12/20, small 10/20, row 12/16, label 10/20 + 0.8px tracking — replacing ~50 ad-hoc sizes (13/12.5/14/15/17…). Sidebar rows raised to 12px by decision; category labels stay 10.
  • 4px grid discipline (8px rhythm) across the shell and app pages; geometry-locked exceptions documented inline.

Editor

  • Fira Code 12/20 (bundled via @fontsource/fira-code), 16px inset, chromeless right-aligned gutter at 40% ink, editorBg token over githubDark's #0d1117 (Prec.high — the theme prop outranks plain extensions).
  • The Mod-F search panel restyled to the shell idiom (the stock CM panel shipped UA-grey gradients).

Drawers

  • Slide-in from the right, docs-assistant chrome and shadow, starting below the 72px top bar. Full height was tried first (the docs assistant's own geometry) and reverted: measured, a 400px drawer covered every top-bar control from 100vw - 400px rightward — not only the panel triggers but Fork/Save, Download, the theme toggle and the account menu. That bar is a workspace toolbar, not a docs-site header.
  • Ask AI: welcome + framed suggestion grid, accent user bubble with tail, flush composer with ↑ send, centered disclaimer.
  • Style: 24px inset alignment, Theme Builder row layout (label + ⓘ tooltip left, control right), floating pickers with Common/Pick-color tabs and collapsible swatch groups, muted-track segmented controllers, framed component list shared with the chat suggestions, dismissible intro notice, brand chip leading the primary ramp (Generate button removed — the chip's onChange already regenerates), AI tab on the chat composer.

Classes over inline styles

  • Drawer registers its stylesheet through the new installCss; panel rules live in src/panels.css on --hot-* tokens (font stacks exposed as --hot-font-*). The ADR-0026 inline-background-vs-hover trap disappears where classes took over.

E2E

Updated where intent changed: keyboard activation for panel switching under full-height drawers, transparent composer measured against the surface behind it, edit box / token rows located by class and data-token instead of inline-style sniffing.

Test plan

  • pnpm typecheck (editor-shell, authoring)
  • pnpm e2e (full suite, after merging master) — 215 passed, 144 skipped (gated suites). The 6 share-view.spec.ts failures in a bare vite preview are the /d/:id worker routes, which need the API worker; they pass in CI.
  • Visual pass in light and dark: editor, search panel, both drawers, all four Style tabs

🤖 Generated with Claude Code


Note

Medium Risk
Broad visual and layout rewrite of the editor, drawers, and Style/Ask AI panels, plus e2e locator changes. No auth or data-path changes, but regressions in contrast, drawer geometry, and panel tests are likely if tokens or classes drift.

Overview
Puts the runner on a single type scale (theme.type: 12/20 base, 10/20 small, 12/16 row, 10/20 label) and a 4px/8px spacing grid, replacing ad-hoc 13px sizes across the shell, admin, and panels.

Editor: bundles Fira Code 400/700, chromeless 12/20 gutter on editorBg, and restyles CodeMirror’s search panel to shell tokens.

Drawers: slide in below the 72px top bar (so toolbar actions stay clickable), share docs-assistant chrome via installCss. Ask AI gets an accent bubble (readable inline code/links), framed suggestions, and a flush auto-growing composer with ↑ send. Style matches Theme Builder rows (ⓘ tooltips, floating Common/Pick-color pickers), a dismissible intro, brand chip on the primary ramp, and the same composer on the AI tab.

Panel rules move to panels.css on --hot-* tokens. E2E follows: offsetWidth for drawer width, data-token/classes instead of inline-style sniffing, contrast checks on the user bubble, and “Send” instead of colliding “Style”.

Reviewed by Cursor Bugbot for commit 2c463c4. Bugbot is set up for automated code reviews on this repo. Configure here.


Review follow-up (post-merge)

origin/master merged in (49 commits; one conflict in icons/ui.tsx, adjacent IconInfoCircle / IconKey imports). That surfaced the CI failure: style-panel.spec.ts's downgrade test, added to master by 39165b2 after this branch cut, closes the Style panel by clicking its own trigger — which the full-height drawer covered. Fixed by the geometry change above rather than by rewriting the test.

Also addressed: the dark tab-strip rule drew border on surfaceRaised and was invisible (ADR-0028); both composers had a max-height no code ever grew into, so rows={1} meant a six-line question in a 44px slot; the AI tab's footer swap hid the unlinked-theme and Google-font notes (Bugbot); the AI send button's "Style" name collided with the top-bar trigger's; theme.type.label was unused while its own call site stayed hand-written; DocsCascader kept six fontSize: 13; only Fira Code 400 was bundled against a theme that sets fontWeight: bold.

Known and left: the .hot-info tooltip is clipped by the drawer body's overflow: auto, the same bucket as the popovers' documented scroll-extension trade. No clean CSS fix exists — the body has to scroll — and a portaled floating element is more than a review fix.

…/grid scales

A design-alignment pass over the authoring shell and both drawers, built
against Figma 48:6560 (light) / 31:6438 (dark) and the docs assistant:

- Typography: one scale in theme.type (base 12/20, small 10/20, row 12/16,
  label 10/20+tracking) replacing ~50 ad-hoc sizes; sidebar rows raised to
  12px by decision, category labels stay 10.
- Spacing: 4px grid discipline (8px rhythm) across shell and app pages;
  geometry-locked exceptions documented inline.
- Editor: Fira Code 12/20 (bundled via @fontsource), 16px inset, chromeless
  gutter, editorBg token over githubDark's #0d1117 (Prec.high — the theme
  prop outranks plain extensions); Mod-F search panel restyled to shell idiom.
- Drawers: full-height, slide-in from the right, docs-assistant chrome;
  Ask AI transcript/composer restyled after the Docs assistant; Style panel
  aligned on the 24px inset with Theme Builder's row layout (label + info
  tooltip left, control right), floating pickers with Common/Pick-color tabs,
  muted-track segmented controllers, framed menu lists shared with the chat
  suggestions, dismissible intro notice, and the AI tab on the chat composer.
- Classes over inline styles: Drawer registers its stylesheet via installCss;
  the panels' rules live in panels.css with tokens as CSS variables
  (--hot-font-* added); the ADR-0026 hover-vs-inline trap disappears with it.
- Tooltips for the two toolbar CTAs share one treatment; thin scrollbars
  shell-wide; theme toggle moved between Download and the avatar; lighter
  hover token in light mode (0.16 -> 0.10).

E2E updated where intent changed: full-height drawers cover the top-bar
triggers (switching is keyboard-reachable, tests activate via Enter), the
composer is transparent over the drawer surface, the edit box and token rows
are located by class/data-token instead of inline-style sniffing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread runner/apps/authoring/src/StylePanel.tsx
demtario and others added 2 commits August 21, 2026 13:05
The full-height drawer covered every top-bar control from `100vw - 400px`
rightward: not just the two panel triggers the comment named, but Fork/Save,
Download, the theme toggle and the account menu. Measured with
`elementFromPoint` on an authed bar. That bar is a workspace toolbar rather
than a docs-site header — "restyle the demo, then save it" is the panel's
main flow, and Ctrl+S was the only way left to finish it — so the panel goes
back below the bar. The two e2e tests that had switched to keyboard
activation go back to plain clicks, and `style-panel.spec.ts`'s downgrade
test, which master added after this branch cut and which closes the panel by
clicking its trigger, passes again. That test is the one failing CI.

The rest, in the order they were found:

* The Style tab strip drew its bottom rule in `border` over a `surfaceRaised`
  fill. Dark `border` *is* `surfaceRaised` (#222222), so the separator was
  invisible — the ADR-0028 trap `panels.css` warns about in its own header.
  `controlBorder`, as the drawer's three hairlines already use.

* Both composers ship `rows={1}` under a `max-height: 128px` that nothing
  ever set a height against, so a six-line question scrolled inside a 44px
  slot with `resize: none` removing the way out. `useAutoGrow` makes the cap
  real; keying it on the value shrinks the field back after a send.

* The AI tab swaps the whole footer for its composer, which left the
  unlinked-theme warning and the Google-font note with nowhere to mount
  (Bugbot). AI styling writes the module through the same `apply`, so an
  example the panel cannot wire produced a preview that did not change and no
  explanation on the tab. Both notes now render in the tab's own section.

* The AI composer's send button was named "Style", the top-bar trigger's
  name, which made any page-scoped `getByRole("button", { name: "Style" })`
  ambiguous while that tab was open. "Send", as the chat panel's is.

* `theme.type.label` was added by the type-scale pass and never used, while
  its documented call site — `SectionHeader.headerLabel` — stayed
  hand-written at the same 10/20/0.8px. Adopted; computed style is unchanged.

* `type.small`'s doc comment pointed at the box description and badge label,
  which this branch deliberately raised to 12 for the same reason `row`
  records. The comment now describes where `small` is actually used.

* `DocsCascader` kept six `fontSize: 13` declarations, one of them on a line
  this branch had already rewritten.

* Only Fira Code 400 was bundled, but the CodeMirror github theme sets
  `fontWeight: bold` on several token classes, so those rendered faux-bold.

* `segmentBtn` and `segment` draw the same control and had drifted apart on
  `fontWeight`.

Left as a reported finding: the `.hot-info` tooltip is clipped by the drawer
body's own `overflow: auto`, in the same acknowledged bucket as the popovers'
"extends the body's scroll rather than flipping upward". There is no clean
CSS fix — the body has to scroll — and a portaled floating element is new
architecture rather than a review fix.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Comment thread runner/apps/authoring/src/panels.css
The user turn became an accent-filled bubble, but the two inline styles in
`markdown.tsx` that carry their own colour were still written for the muted
surface it replaced.

`codeStyle` sets no `color`, so an inline code chip inherited the bubble's
`accentContrast` (#ffffff) onto its own `surfaceMuted` fill (#f7f7f9 in
light) — white on near-white. `linkStyle` is `accentText`, which in light *is*
`accent` (#1A42E8), so a link on the bubble was the bubble: measured contrast
1.0, i.e. invisible. Dark was better but still under AA at 2.27. A question
that names an option in backticks is the ordinary case, and it came out blank.

`Markdown` takes an `onAccent` flag, threaded through `renderInline` so nested
inlines get it too, and the chat passes it for user turns only. The variants
are white-alpha rather than tokens on purpose: `accent` is mode-invariant
("Handsontable blue — brand"), so what sits on it is mode-invariant too, and
no token means "translucent over the accent". Links underline, because the one
colour guaranteed to read there is the one the surrounding text already uses.
Fenced blocks needed nothing — `preStyle` sets its own `color` on `editorBg`.

The regression test measures a contrast ratio rather than colour inequality:
the light defect was #ffffff on #f7f7f9, two colours that differ and are still
unreadable. Verified to fail with the fix reverted (1.0 light / 2.27 dark) and
pass with it, in both modes. It needs its own `contrastPair` helper — the
existing `contrast(page, selector)` walks up to the first fill with a non-zero
alpha, which would stop at the chip's own translucent white and measure white
against white instead of against the accent showing through it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit db5b9ff. Configure here.

Comment thread runner/e2e/panels.spec.ts
demtario and others added 2 commits August 21, 2026 14:02
Bugbot on db5b9ff, and it is right. The inline-code half of the assertion
took the glyph colour against the bubble's opaque accent fill, which is only
the truth while the chip is translucent. The regression it is supposed to
catch paints an *opaque* `surfaceMuted` chip and still inherits white:
`getComputedStyle` reports white either way, white against the accent clears
4.5, and the defect ships green.

The revert check I ran before committing it did fail — but on "link on the
bubble" both times. The code half passed with the fix reverted and I read the
two failures as the two halves.

Now composited: `over(chip fill, bubble fill)` is what the glyphs actually sit
on, and the two states differ exactly there. Re-verified with the fix reverted
— light fails on inline code at 1.07 (white on #f7f7f9, the original defect)
and dark on the link at 2.27 — and passes with it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CI failed on the width test with 400 against 399.99993896484375. It is the
entrance animation this branch added: the drawer keeps a `translateX` — 0 once
settled, but a transform all the same — so it sits on a composited layer whose
`getBoundingClientRect()` comes back through float matrix maths, and the test
was racing the 0.2s transition.

Sampled across the animation locally, the rect reads 400.00006103515625 at
t+90ms while `offsetWidth` holds 400 at every point; CI landed the same error
in the other direction. Layout width is also what "both drawers share one
width" means — the CSS contract, not the rendered geometry — so this removes
the race rather than waiting the transition out.

Only this assertion was exposed: the suite's other `boundingBox()` callers
measure untransformed elements or compare relatively.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@demtario
demtario merged commit 23b5ac3 into master Aug 21, 2026
7 checks passed
@demtario
demtario deleted the feature/styles-with-ai-sidebar branch August 21, 2026 12:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants